using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using SharpFastCGI.Internals;

// Notes:
// 
// The Web server sends environment variables through FCGI_PARAMS, and
// content through FCGI_STDIN.
// The responder replies through FCGI_STDOUT and FCGI_STDERR after all
// FCGI_PARAMS have been received.
// 
// The number of bytes to be excepted through FCGI_STDIN is contained in
// the CONTENT_LENGTH env. variable.

namespace SharpFastCGI
{
	public abstract class Server
	{
		private const int bufferSize = 1024;
		
		protected int mNumberOfThreads;
		protected int mSizeOfBuffers;
		
		private IApplicationFactory mApplicationFactory;
		
		private string[] mTypicalEnvVar;
		private Dictionary<string,int> mTypicalEnvVarIndices;
		private int mTypicalEnvVarLength;
		
		private bool mRunning;
		
		/// <summary>
		/// Returns the maximal number of connections that can wait to be accepted.
		/// </summary>
		/// <returns>
		/// The maximal number of connections that can wait to be accepted.
		/// </returns>
		protected abstract int GetMaxSimultaneousConnections();
		
		/// <returns>The Stream of a new connection, or a null reference if the server is closed.</returns>
		/// <remarks>The method must be thread-safe (concurrent accept).</remarks>
		protected abstract Stream Accept();
		
		/// <summary>
		/// Returns an array with the name of environment variables typically provided by the remote server.
		/// </summary>
		/// <returns>
		/// An array with the name of environment variables typically provided by the remote server.
		/// </returns>
		protected abstract string[] TypicalEnvironmentVariables();
		
		public bool IsRunning
		{
			get { return mRunning; }
		}
		
		public IApplicationFactory ApplicationFactory
		{
			set { mApplicationFactory = value; }
			get { return mApplicationFactory; }
		}
		
		/// <summary>
		/// Starts listening to the remote server.
		/// </summary>
		public virtual void Start()
		{
			if(mRunning) throw new InvalidOperationException();
			
			mRunning = true;
			
			mTypicalEnvVarIndices = new Dictionary<string,int>();
			int idx = 0;
			mTypicalEnvVar = TypicalEnvironmentVariables();
			foreach(string name in mTypicalEnvVar)
			{
				mTypicalEnvVarIndices.Add(name, idx++);
			}
			mTypicalEnvVarLength = idx;
			
			for(int i = 0 ; i < mNumberOfThreads ; ++i)
			{
				Thread t = new Thread(Task);
				t.Start();
			}
		}
		
		/// <summary>
		/// Stops listening to the remote server.
		/// </summary>
		/// <remarks>
		/// The overriding method must make it so that all calls to Accept()
		/// returns a null reference immediatly.
		/// </remarks>
		public virtual void Stop()
		{
			if(!mRunning) throw new InvalidOperationException();
			
			mTypicalEnvVarIndices = null;
			mTypicalEnvVar = null;
			mRunning = false;
		}
		
		private void Task()
		{
			Transport t = new Transport(this);
			Stream s;
			while((s = Accept()) != null)
			{
				t.Process(s);
				t.Recycle();
			}
		}
		
		protected virtual string GetValue(string Key)
		{
			switch(Key)
			{
				case "FCGI_MAX_CONNS":
					return GetMaxSimultaneousConnections().ToString();
				case "FCGI_MAX_REQS":
					return GetMaxSimultaneousConnections().ToString();
				case "FCGI_MPXS_CONNS":
					return "0";
				default:
					return null;
			}
		}
		
		
		private class Transport : IRecordHandler // TODO
		{
			// Parent Server
			private Server mServer;
			
			// Application Instances
			private IApplication[] mRoles;
			
			// Standard Streams
			private InputStreamImpl mStdin;
			private OutputStreamImpl mStdout;
			private OutputStreamImpl mStderr;
			
			// Buffers
			private InputBuffer mInBuffer;
			private OutputBuffer mOutBuffer;
			
			// Environment Variables
			private HybridDictionary mHybridDic;
			
			// Current Stream
			private Stream mStream;
			
			public Transport(Server Svr)
			{
				mServer = Svr;
				
				mInBuffer = new InputBuffer(bufferSize, this);
				mOutBuffer = new OutputBuffer(bufferSize);
				
				string[] mValueOfTypicalEnvVar = new string[Svr.mTypicalEnvVarLength];
				int[] mTypicalEnvVarSet = new int[mValueOfTypicalEnvVar.Length];
				LightDictionary<string,string> mOtherEnvVar = new LightDictionary<string,string>();
				
				mHybridDic = new HybridDictionary(Svr.mTypicalEnvVar,
				                                  mValueOfTypicalEnvVar,
				                                  mTypicalEnvVarSet,
				                                  Svr.mTypicalEnvVarIndices,
				                                  mOtherEnvVar);
				
				mRoles = new IApplication[4];
				mRoles[(int)Role.Responder] = mServer.ApplicationFactory.GetResponder();
				mRoles[(int)Role.Filter] = mServer.ApplicationFactory.GetFilter();
				mRoles[(int)Role.Authorizer] = mServer.ApplicationFactory.GetAuthorizer();
				
				mStdin = new InputStreamImpl(this);
				mStdout = new OutputStreamImpl(mOutBuffer, RecordType.Stdout);
				mStderr = new OutputStreamImpl(mOutBuffer, RecordType.Stderr);
			}
			
			public void Process(Stream S)
			{
				mInBuffer.Initialize(S);
				mOutBuffer.Initialize(S);
				
				Role role = mInBuffer.WaitBeginRequestRecord();
				IApplication app = mRoles[(int)role];
				
				try
				{
					int exitCode = app.Run(mHybridDic, mStdin, mStdout, mStderr);
					mStderr.Close();
					mStdout.Close();
					mOutBuffer.Write_EndRequest(1, exitCode, ProtocolStatus.RequestComplete);
					mOutBuffer.Flush();
				}
				catch(Exception ex)
				{
					StreamWriter wtr = new StreamWriter(stderr);
					wtr.WriteLine(ex.ToString());
					wtr.Close();
					mStderr.Close();
					mStdout.Close();
					mOutBuffer.Write_EndRequest(1, 999, ProtocolStatus.RequestComplete);
					mOutBuffer.Flush();
				}
			}
			
			public void Recycle()
			{
				mHybridDic.Clear();
				mStdout.Recycle();
				mStderr.Recycle();
			}
		}
		
		private class InputStreamImpl : InputStream // TODO
		{
			private InputBuffer mInBuffer;
			
			public bool IsClosed
			{
				get { return mTransport.IsClosed; }
			}
			
			public int Read(byte[] Buffer, int Offset, int Length)
			{
				// Read and process all records until we get a stdin record
				ProcessRecordsUntilType(RecordType.Stdin);
				// Now, we have a stdin record
				FillBuffer(1);
				int dataReady = Math.Min(mTransport.mReadBufferDataLength - mTransport.mReadBufferDataOffset, Length);
				//Array.Copy(mTransport.mReadBuffer, 
			}
			
			public void Close()
			{
				mTransport.Close();
			}
		}
		
		private class OutputStreamImpl : OutputStream
		{
			private OutputBuffer mOutBuffer;
			private RecordType mType;	// Stdout, or Stderr
			private bool mClosed;
			private bool mUsed;
			
			public bool IsClosed
			{
				get { return mClosed; }
			}
			
			public OutputStreamImpl(OutputBuffer Output, RecordType Type)
			{
				mOutBuffer = Output;
				mType = Type;
			}
			
			public void Recycle()
			{
				mClosed = false;
				mUsed = false;
			}
			
			public int Write(byte[] Buffer, int Offset, int Length)
			{
				if(Buffer == null) throw new ArgumentNullException("Buffer");
				if(Offset < 0 || Length < 0) throw new ArgumentOutOfRangeException();
				if(Buffer.Length - Offset < Length) throw new ArgumentException("Offset+Length", "The size of the buffer is less than Offset+Length.");
				
				if(mClosed) return -1;
				
				if(Length == 0) return 0;
				mOutBuffer.StdOutErr_Stream(1, mType, Buffer, Offset, Length);
				mUsed = true;
				return Length;
			}
			
			public void Flush()
			{
				if(mClosed) return;
				
				mOutBuffer.Flush();
			}
			
			public void Close()
			{
				if(mClosed || !mUsed) return;
				mOutBuffer.Write_StdOutErr_CloseStream(1, mType);
				mClosed = true;
			}
		}
	}
}
